package books;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;

import rules.bookinfo.BookInfoValidator;
import books.search.SearchByExamplePredicate;
import exceptions.BookInfoAlreadyExistsException;
import exceptions.BookInfoDoesNotExistException;
import exceptions.BookNotAvailableException;
import exceptions.BooksException;
import exceptions.IsbnAlreadyExistsException;

/**
 * Handler of Book and BookInfo's. Knows how to register a BookInfo, add a new copy, and obtain a copy.
 */
public class BooksHandler {

    private Map<BookInfo, BookInfoHome> books;

    public BooksHandler() {
        this.books = new HashMap<BookInfo, BookInfoHome>();
    }

    public void addBookInfo(final BookInfo info) {
        this.validateBookInfo(info);
        this.getBooks().put(info, new BookInfoHome(info));
    }

    public void validateBookInfo(final BookInfo info) {
        new BookInfoValidator().validate(info);
        if (this.getBooksInfo().contains(info)) {
            throw new BookInfoAlreadyExistsException();
        }
        for (BookInfo bi : this.books.keySet()) {
            if (bi.getIsbn().equalsIgnoreCase(info.getIsbn())) {
                throw new IsbnAlreadyExistsException();
            }
        }
    }

    public void addNewBookCopy(final Book aCopy) {
        if (this.codeExists(aCopy.getCode())) {
            throw new BooksException("The code of the copy already exists");
        }
        this.getBookInfoHome(aCopy.getInfo()).addNewCopy(aCopy);
    }

    private boolean codeExists(final long code) {
        // deberia haber algo mas buena onda
        return code > 2323;
    }

    public Map<BookInfo, BookInfoHome> getBooks() {
        return this.books;
    }

    public void setBooks(final Map<BookInfo, BookInfoHome> books) {
        this.books = books;
    }

    public Collection<BookInfo> getBooksInfo() {
        return this.books.keySet();
    }

    public List<Book> getAllCopies(final BookInfo aBookInfo) {

        return this.getBookInfoHome(aBookInfo).getAllCopies();
    }

    public BookInfoHome getBookInfoHome(final BookInfo aBookInfo) {
        this.validateGetBookInfo(aBookInfo);
        return this.getBooks().get(aBookInfo);
    }

    public Book borrowACopy(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).borrowACopy();
    }

    public void validateGetBookInfo(final BookInfo aBookInfo) {
        if (!this.getBooksInfo().contains(aBookInfo)) {
            throw new BookInfoDoesNotExistException();
        }
        if (!aBookInfo.isAvailable()) {
            throw new BookNotAvailableException();
        }
    }

    // ------------------------------------------------------------------//

    public boolean containsBookInDatabase(final String anISBN) {
        Set<BookInfo> data = this.books.keySet();
        boolean ret = false;

        for (BookInfo actual : data) {
            if (actual.getIsbn().equals(anISBN)) {
                ret = true;
                break;
            }
        }

        return ret;
    }

    public void returnCopy(final Book aCopy) {
        this.getBookInfoHome(aCopy.getInfo()).returnCopy(aCopy);
    }

    public List<Book> getAvailableCopies(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).getAvailableCopies();
    }

    public List<Book> getBorrowedCopies(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).getBorrowedCopies();
    }

    public int getTimesBorrowed(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).getTimesBorrowed();
    }

    // public void addCopiesOfBook(final BookInfo bookInfo, final int copies) {
    // for (int i = 0; i < copies; i++) {
    // this.addNewBookCopy(new Book(bookInfo, CopyCodeCounter.useNextCode()));
    // }
    // }

    public Collection<BookInfo> searchByExample(final BookInfo example) {
        Collection<BookInfo> bookInfos = this.getBooksInfo();
        @SuppressWarnings("unchecked")
        Collection<BookInfo> result = CollectionUtils.select(bookInfos, new SearchByExamplePredicate(example));

        return result;
    }
}
